﻿#region Description
/*  File Name:      ScreenManager.cs
 *  Author:         Adam Goodchild
 *  
 *  Description:    This class manages all of the screens in the game.
 *                  It contains a list of all screens, and handles the
 *                  Loading, Updating, Drawing and Input of the Screens.
 *                  By default, the top most screen will have active Input.
 *  
 * Sources:         The Idea for this class was gained from the following source(s):
 *                  Microsoft XNA Creators Club - Game State Management Sample
*/
#endregion

#region Using Statements
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using MinimizeEngine.Screens;
#endregion

namespace MinimizeEngine.Managers
{
    /// <summary>
    /// See File Description at the top of the File.
    /// </summary>
    public class ScreenManager : DrawableGameComponent
    {
        #region Fields
        /// <summary>
        /// This is a list of all Screens held in the Game
        /// </summary>
        List<BaseScreen> mScreens = new List<BaseScreen>();
        
        /// <summary>
        /// This list will hold all of the screens that need to be
        /// updated.
        /// </summary>
        List<BaseScreen> mScreensToBeUpdated = new List<BaseScreen>();

        /// <summary>
        /// This is the Input State Management
        /// </summary>
        InputManager mInputManager = new InputManager();

        SpriteBatch mSpriteBatch;
        SpriteFont mSpriteFont;
        Texture2D mBlankTexture;

        bool m_bIsInitialised;
        bool m_bTraceEnabled = true;

        #endregion

        #region Acessors and Getters
        /// <summary>
        /// This is the default SpriteBatch that all of the Screens will share.
        /// Doing it this way will save each screen having to create their own
        /// SpriteBatch, saving time and resources.
        /// </summary>
        public SpriteBatch SpriteBatch
        {
            get { return mSpriteBatch; }
        }

        /// <summary>
        /// This is the default Font that all Screens will share. Other Fonts 
        /// can be used when needed, otherwise this default Font is used in 
        /// every Screen. Saves time and resources instead of making each Screen
        /// load their own Font.
        /// </summary>
        public SpriteFont Font
        {
            get { return mSpriteFont; }
        }

        /// <summary>
        /// DEBUG PURPOSES ONLY
        /// If enabled, the Screen Manager can print out a list of all Screens
        /// when they are updated. Provides a check to ensure all Screens are
        /// Added and Removed correctly.
        /// </summary>
        public bool TraceEnabled
        {
            get { return m_bTraceEnabled; }
            set { m_bTraceEnabled = value; }
        }
        #endregion

        #region Initialization

        /// <summary>
        /// Constructor for the ScreenManager class.
        /// </summary>
        /// <param name="game"></param>
        public ScreenManager(Game game) : base(game)
        {

        }

        /// <summary>
        /// Initializes the Screen Manager
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            //Set the Screen Manager Initialization to true when its done
            m_bIsInitialised = true;
        }

        /// <summary>
        /// Loads all of the Graphics Content
        /// </summary>
        protected override void LoadContent()
        {
            //Load in all of the Content belonging to the Screen Manager
            ContentManager contentManager = Game.Content;

            mSpriteBatch = new SpriteBatch(GraphicsDevice);
            mSpriteFont = contentManager.Load<SpriteFont>("Fonts/CourierNew");
            mBlankTexture = new Texture2D(GraphicsDevice, 4, 4);

            //Now we tell each Screen to load all of their Content
            foreach (BaseScreen baseScreen in mScreens)
            {
                baseScreen.LoadContent();
            }
        }

        protected override void UnloadContent()
        {
            //Make sure all of the Screens unload their Content
            foreach (BaseScreen baseScreen in mScreens)
            {
                baseScreen.UnloadContent();
            }
        }
        #endregion

        #region Update

        public override void Update(GameTime gameTime)
        {
            //Make sure we read the Keyboard and Gamepad Input
            mInputManager.Update();

            //To ensure that there is no conflicts with the Updating of Screens
            //we create a copy of the Master Screen List.
            mScreensToBeUpdated.Clear();

            foreach (BaseScreen baseScreen in mScreens)
            {
                mScreensToBeUpdated.Add(baseScreen);
            }

            //A boolean to see if another screen has focus
            bool otherScreenHasFocus = !Game.IsActive;

            //A boolean to see if the Screen is covered by another Screen
            bool coveredByOtherScreen = false;

            //Main Update Loop
            //This will loop through as long as their is a Screen to be Updated
            while (mScreensToBeUpdated.Count > 0)
            {
                //Take the top screen off of the Screen Updated List
                BaseScreen baseScreen = mScreensToBeUpdated[mScreensToBeUpdated.Count - 1];
                mScreensToBeUpdated.RemoveAt(mScreensToBeUpdated.Count - 1);

                //Now Update the Screen
                baseScreen.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);

                if (baseScreen.ScreenState == ScreenState.TransitionOn ||
                    baseScreen.ScreenState == ScreenState.Active)
                {
                    //If this Screen is the first Active Screen we have found
                    //then allow it to Handle its Input
                    if(!otherScreenHasFocus)
                    {
                        baseScreen.HandleInput(mInputManager);

                        otherScreenHasFocus = true;
                    }

                    //If this Screen is Active, and it is NOT a Popup,
                    //make sure that all other screens know they are covered
                    //by this screen.
                    if (!baseScreen.IsPopup)
                    {
                        coveredByOtherScreen = true;
                    }
                }
            }

            //Shall we Print the Debug Trace??
            if(m_bTraceEnabled)
            {
                TraceScreens();
            }
        }

        #endregion

        #region Draw

        public override void Draw(GameTime gameTime)
        {
            foreach (BaseScreen baseScreen in mScreens)
            {
                if (baseScreen.ScreenState == ScreenState.Hidden)
                {
                    continue;
                }

                baseScreen.Draw(gameTime);
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Add a new Screen for the ScreenManager to handle
        /// </summary>
        /// <param name="BaseScreen"></param>
        /// <param name="controllingPlayer"></param>
        public void AddScreen(BaseScreen baseScreen, PlayerIndex? controllingPlayer)
        {
            baseScreen.ControllingPlayer = controllingPlayer;
            baseScreen.ScreenManager = this;
            baseScreen.IsExiting = false;

            //If we are Initialized, and we have got a Graphics Device,
            //then tell the Screen to Load its Content
            if(m_bIsInitialised)
            {
                baseScreen.LoadContent();
            }

            mScreens.Add(baseScreen);
        }

        /// <summary>
        /// This allows us to Remove a Screen from the Screen Manager.
        /// By default, BaseScreen.ExitScreen should be called, so that
        /// the Screen has time to Transition off, rather than it disappearing.
        /// </summary>
        /// <param name="BaseScreen"></param>
        public void RemoveScreen(BaseScreen baseScreen)
        {
            //If we are Initialized, and we have got a Graphics Device,
            //then tell the Screen to Unload its Content
            if(m_bIsInitialised)
            {
                baseScreen.UnloadContent();

                mScreens.Remove(baseScreen);
                mScreensToBeUpdated.Remove(baseScreen);
            }
        }

        /// <summary>
        /// Return an Array of all of the Screens.
        /// A Copy is returned, so that Screens cannot be Added
        /// or Removed without using the AddScreen or RemoveScreen methods.
        /// </summary>
        /// <returns></returns>
        public BaseScreen[] GetScreens()
        {
            return mScreens.ToArray();
        }

        /// <summary>
        /// This is a helper function to draw a translucent sprite, of 
        /// the specified colour. This is used for fading Screens in and out,
        /// and for a nice colour behind the PopUp Screens.
        /// </summary>
        /// <param name="alpha"></param>
        public void FadeBackBufferToColour(Color color, int alpha)
        {
            Viewport viewport = GraphicsDevice.Viewport;

            mSpriteBatch.Begin();
            mSpriteBatch.Draw(mBlankTexture,
                                new Rectangle(0, 0, viewport.Width, viewport.Height),
                                new Color(color, (byte)alpha));
            mSpriteBatch.End();
        }

        #endregion

        #region Debugging

        /// <summary>
        /// DEBUGGING PURPOSES ONLY
        /// Print a List of all Screens
        /// </summary>
        private void TraceScreens()
        {
            List<String> screenNames = new List<String>();

            foreach (BaseScreen baseScreen in mScreens)
            {
                screenNames.Add(baseScreen.GetType().Name);
            }

            Trace.WriteLine(string.Join(", ", screenNames.ToArray()));
        }

        #endregion
    }
}
